/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package jgaptest;

import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.util.ArrayList;
import java.util.Arrays;
import org.jgap.FitnessFunction;
import org.jgap.Gene;
import org.jgap.IChromosome;

/**
 *
 * @author jkanazawa
 */
public class MaeFitnessFunction extends FitnessFunction {

    private int[][] img0;
    private int[][] imgX;
    private Operador op;
    private final double maxError;
    private int w = 0;
    private int h = 0;
    BufferedImage bimg0;

    MaeFitnessFunction(BufferedImage img0, BufferedImage imgX) {
        //this.img0 = Operations.loadData(img0);
        bimg0 = img0;
        this.imgX = Operations.loadData(imgX);
        this.op = new Operador(img0);
        this.maxError = 256;
        w = img0.getWidth();
        h = img0.getHeight();
    }

    @Override
    protected double evaluate(IChromosome cromosoma) {
        if (!isValid(cromosoma)) {
            return 0;
        }
        this.img0 = Operations.loadData(bimg0);
        Integer nroOperacion;
//        BufferedImage img1 = new BufferedImage(img0.getWidth(), img0.getHeight(), img0.getType());
//        img1.setData(img0.getData());
        for (Gene g : cromosoma.getGenes()) {

            nroOperacion = (Integer) g.getAllele();

            img0 = op.aplicarOperacion(nroOperacion.intValue(), img0);

        }
        double val1 = 255 - distanciaDestino(img0);//this.maxError - distanciaDestino(img0);
        double val2 = 0;//(w * h) / jensenShannonDivergence(this.calcHisto(img0), this.calcHisto(imgX));
        double val = (val1 + val2);
        //val = 100*val1/jensenShannonDivergence(this.calcHisto(img0), this.calcHisto(imgX));//0.6 * (val1/val) + 0.4 * val2/val;//(1000 / jensenShannonDivergence(this.calcHisto(img0), this.calcHisto(imgX)));
        //System.out.println("JSD: " +val2);// jensenShannonDivergence(this.calcHisto(img0), this.calcHisto(imgX)));
        return val1;

    }

    private boolean isValid(IChromosome cromosoma) {
        //13, 16, 17, 20
        int[] prev = new int[cromosoma.getGenes().length];
        int cnt = 0;
        boolean ret = true;
        for (Gene g : cromosoma.getGenes()) {
            int nroOp = (Integer) g.getAllele();
            if (nroOp == 13 || nroOp == 16 || nroOp == 17 || nroOp == 20) {
                for (int i = 0; i < cnt; i++) {
                    if (prev[i] == 15) {
                        return true;
                    }

                }
                return false;
            }
            prev[cnt] = nroOp;
            cnt++;
        }
        return false;
    }

    public double distanciaDestino(int[][] img0) {
        double dist = 0.0;

//        Raster r0 = img0.getData();
//        Raster r1 = imgX.getData();

        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h; j++) {
                //for (int k = 0; k < r0.getNumBands(); k++) {
                dist += Math.abs(imgX[i][j] - img0[i][j]);
                //}
            }
        }

        dist = (double) dist / (double) (w * h);

        return dist;
    }

    /**
     *Jensen-Shannon divergence.
     */
    public static double jensenShannonDivergence(double[] p1, double[] p2) {
        assert (p1.length == p2.length);
        double[] average = new double[p1.length];
        for (int i = 0; i < p1.length; ++i) {
            average[i] += (p1[i] + p2[i]) / 2;
        }
        return (klDivergence(p1, average) + klDivergence(p2, average)) / 2;
    }
    public static final double log2 = Math.log(2);

    /**
     * Returns the KL divergence, K(p1 || p2).
     *
     * The log is w.r.t. base 2. <p>
     *
     * *Note*: If any value in <tt>p2</tt> is <tt>0.0</tt> then the
     * KL-divergence is <tt>infinite</tt>. Limin changes it to zero instead of
     * infinite.
     *
     */
    public static double klDivergence(double[] p1, double[] p2) {


        double klDiv = 0.0;

        for (int i = 0; i < p1.length; ++i) {
            if (p1[i] == 0) {
                continue;
            }
            if (p2[i] == 0.0) {
                continue;
            } // Limin

            klDiv += p1[i] * Math.log(p1[i] / p2[i]);
        }

        return klDiv / log2; // moved this division out of the loop -DM
    }

    private double[] calcHisto(int[][] data) {
        double[] hist = new double[256];
        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h; j++) {
                hist[data[i][j]] += 1;
            }
        }
        return hist;
    }
}
